import { basename, extname, join, normalize } from "path";
import Config from "../core/Config";
import { Extensions, FileResult, TinifyConfig } from "../core/Defines";
import { Environment } from "../core/Environment";
import { FileUtils } from "../core/FileUtils";
import { existsSync, statSync, writeFileSync } from "fs";
import { CommonUtils } from "../core/CommonUtils";

interface Records {
    successCount: number,
    failedCount: number,
    successInfo: string,
    failedInfo: string,
    successAssets: FileResult[],
    failedAssets: FileResult[],
}

export default class Helper extends Config<TinifyConfig> {

    module = "【Tinypng 压缩】";

    readonly defaultData: TinifyConfig | null = {
        API_KEY: "",
        excludeFolders: "",
        excludeFiles: "",
        compressDir: "E:\\exer\\cocos\\snack\\res",
        compressTypeList: {
            "png": {
                name: "png",
                checked: true
            },
            "jpg": {
                name: "jpg",
                checked: true
            },
            "webp": {
                name: "webp",
                checked: true
            },
            "jpeg": {
                name: "jpeg",
                checked: true
            }
        }
    };

    get path() {
        let out = join(this.configPath, `${Extensions.TinifyCompress}.json`);
        return out;
    }

    get data() {
        if (!this._data) {
            this.read(true);
        }
        return this._data;
    }
    /**@description 获取当前的日期 */
    get date() {
        let date = new Date();
        return CommonUtils.formatDate("yyyyMMddhhmmss", date);
    }

    /**@description 需要排除的文件目录 */
    private get excludeFolders() {

        let content = this.data!.excludeFolders.replace(/\n/g, ",")
        // 需要排除的文件夹
        let excludeFolders = content.split(",").map(value => value.trim());
        //去除空的
        let i = excludeFolders.length;
        while (i--) {
            if (!!!excludeFolders[i]) {
                excludeFolders.splice(i);
            }
        }
        excludeFolders = excludeFolders.map(value => normalize(value));
        return excludeFolders;
    }

    /**@description 需要排除的文件 */
    private get excludeFiles() {
        let content = this.data!.excludeFiles.replace(/\n/g, ",");
        // 需要排除的文件
        let excludeFiles = content.split(",").map(value => value.trim());
        //去除空的
        let i = excludeFiles.length;
        while (i--) {
            if (!!!excludeFiles[i]) {
                excludeFiles.splice(i);
            }
        }
        excludeFiles = excludeFiles.map(value => normalize(value));
        return excludeFiles;
    }

    protected onStartCompress() {
        this.logger.log(`${this.module} 开始压缩...`);
    }

    protected onCompressComplete() {

        this.logger.log(`${this.module} 压缩任务完成`);
    }

    //日志记录
    protected records: Records = null!;

    //压缩队列 
    protected compressTasks: any[] = [];

    /**@description 引擎内置资源 */
    protected get enginPath() {
        if (Environment.isVersion3X) {
            return "main";
        } else {
            return "internal";
        }
    }

    private async validate() {
        const data = this.data;

        const tinify = require("tinify");
        return new Promise<boolean>((resolve, reject) => {
            if (!data) {
                this.logger.error(`${this.module} 配置数据为空，请检查配置文件是否存在`);
                resolve(false);
                return;
            }
            tinify.key = data.API_KEY;
            tinify.validate((err: any) => {
                if (err) {
                    this.logger.error(`${this.module} 验证失败: ${err.message}`);
                    resolve(false);
                    return;
                }
                this.logger.log(`${this.module} 验证成功`);
                resolve(true);
                return;
            });
        });
    }

    protected compress(srcPath: string, isCompress: (info: FileResult) => boolean) {
        let files = FileUtils.getFiles(srcPath, isCompress);
        let totalCount = files.length;
        let curCount = 0;
        this.onStartCompress();
        let self = this;

        files.forEach((fileInfo) => {
            this.compressTasks.push(new Promise((resolve, reject) => {
                const sizeBefore = fileInfo.size / 1024; // KB
                const tinify = require("tinify");
                tinify.fromFile(fileInfo.path).toFile(fileInfo.path, (err: any) => {
                    this.logger.log(`${self.module} 压缩进度: ${curCount + 1}/${totalCount} (${((curCount + 1) / totalCount * 100).toFixed(2)}%) - ${fileInfo.relative}`);
                    curCount++;
                    self.recordResult(err, fileInfo, sizeBefore);
                    resolve(true);
                });
            }))
        });
    }

    private recordResult(err: any, fileInfo: FileResult, sizeBefore: number) {
        if (err) {
            const tinify = require("tinify");
            let msg = "";
            if (err instanceof tinify.AccountError) {
                this.logger.log("The error message is: " + err.message);
                // Verify your API key and account limit.
                let compressionsThisMonth = tinify.compressionCount;
                msg = `Verify your API key and account limit. Error message: ${err.message}. You have compressed ${compressionsThisMonth} files this month.`;
            } else if (err instanceof tinify.ClientError) {
                // Check your source image and request options.
                msg = `Check your source image and request options. Error message: ${err.message}`;
            } else if (err instanceof tinify.ServerError) {
                // Temporary issue with the Tinify API.
                msg = `Temporary issue with the Tinify API. Error message: ${err.message}`;
            } else if (err instanceof tinify.ConnectionError) {
                // A network connection error occurred.
                msg = `A network connection error occurred. Error message: ${err.message}`;
            } else {
                // Something else went wrong, unrelated to the Tinify API.
                msg = `Something else went wrong, unrelated to the Tinify API. Error message: ${err.message}`;
            }
            this.records.failedCount++;
            this.records.failedInfo += `\n - ${'Failed'.padEnd(13, ' ')} | ${fileInfo.relative}`;
            this.records.failedInfo += `\n ${''.padEnd(10, ' ')} - ${msg}`;
            this.records.failedAssets.push(fileInfo);
            this.logger.error(`${this.module} 压缩失败: ${fileInfo.relative}`); 
        } else {
            const fileName = basename(fileInfo.path);
            const sizeAfter = statSync(fileInfo.path).size / 1024;
            const savedSize = sizeBefore - sizeAfter;
            const savedRatio = savedSize / sizeBefore * 100;
            this.records.successCount++;
            this.records.successInfo += `\n - ${'Success'.padEnd(13, ' ')} | ${fileName.padEnd(50, ' ')} | ${(sizeBefore.toFixed(2) + ' KB').padEnd(13, ' ')} ->   ${(sizeAfter.toFixed(2) + ' KB').padEnd(13, ' ')} | ${(savedSize.toFixed(2) + ' KB').padEnd(13, ' ')} | ${(savedRatio.toFixed(2) + '%').padEnd(20, ' ')}`;
            this.records.successAssets.push(fileInfo);
            this.logger.log(`${this.module} 压缩成功: ${this.records.successInfo}`);
        }
    }

    private printResults() {
        this.logger.log(`${this.module} 压缩完成`);
        this.logger.log(`成功压缩: ${this.records.successCount} 个文件`);
        this.logger.log(`失败压缩: ${this.records.failedCount} 个文件`);
        const header = `\n # ${'Result'.padEnd(13, ' ')} | ${'Name / Path'.padEnd(50, ' ')} | ${'Size Before'.padEnd(13, ' ')} ->   ${'Size After'.padEnd(13, ' ')} | ${'Saved Size'.padEnd(13, ' ')} | ${'Compressibility'.padEnd(20, ' ')}`;
        let content = '压缩日志 >>>' + header + this.records.successInfo + this.records.failedInfo;
        this.logger.log(this.module, content);
        //写入本地文件
        // if (this.isWriteLog) {
        this.records.successAssets.sort((a, b) => {
            if (a.name > b.name) {
                return 1;
            } else if (a.name == b.name) {
                return 0;
            }
            else {
                return -1;
            }
        })

        this.records.failedAssets.sort((a, b) => {
            if (a.name > b.name) {
                return 1;
            } else if (a.name == b.name) {
                return 0;
            }
            else {
                return -1;
            }
        })

        let data = {
            success: this.records.successAssets,
            failed: this.records.failedAssets
        }

        let path = join(this.configPath, `${Extensions.TinifyCompress}_${this.date}_cache.json`);
        this.logger.log(`${this.module}写入日志到${path}`);
        writeFileSync(path, JSON.stringify(data), "utf-8");
        // }
    }

    private isCompress(fileInfo: FileResult) {
        let extName = extname(fileInfo.path);
        extName = extName.replace(".", "").toLowerCase();
        if (fileInfo.path.includes(this.enginPath)) {
            //引擎内置资源不压缩
            this.logger.warn(`引擎内置资源不压缩: ${fileInfo.relative}`);
            return false;
        }
        
        // 在 compressTypeList 中查找匹配的类型有没有checked
        // this.logger.log(`检查文件类型: ${extName} - ${fileInfo.relative}`);
        // this.logger.log(`支持的文件类型:`, this.data!.compressTypeList);
        let index = Object.values(this.data!.compressTypeList).findIndex(typeInfo => {
            return typeInfo.name.toLowerCase() == extName && typeInfo.checked;
        });
        if (index < 0) {
            //如果不是需要压缩的文件类型
            this.logger.log(`不支持的文件类型: ${fileInfo.relative}`);
            return false;
        }

        let srcPath = fileInfo.relative;
        this.logger.log(`检查文件: ${fileInfo.relative}`);
        //排除指定
        for (let i = 0; i < this.excludeFolders.length; i++) {
            let tempPath = join(srcPath, this.excludeFolders[i]);
            if (fileInfo.path.startsWith(tempPath)) {
                // this.logger.log(`需要排除目录:${excludeFolders[i]}`);
                return false;
            }
        }

        //排除指定文件
        for (let i = 0; i < this.excludeFiles.length; i++) {
            let tempPath = join(srcPath, this.excludeFiles[i]);
            if (fileInfo.path.startsWith(tempPath)) {
                // this.logger.log(`需要排除文件:${excludeFiles[i]}`);
                return false;
            }
        }

        return true;
    }

    private async startCompress() {
        let srcPath = this.data!.compressDir;
        let excludeFolders = this.excludeFolders;
        let excludeFiles = this.excludeFiles;
        if (excludeFolders.length > 0) {
            this.logger.log(`需要排除目录:`, excludeFolders);
        }
        if (excludeFiles.length > 0) {
            this.logger.log(`需要排除文件:`, excludeFiles);
        }

        //日志重置
        this.records = {
            successCount: 0,
            failedCount: 0,
            successInfo: "",
            failedInfo: "",
            successAssets: [],
            failedAssets: []
        };


        this.logger.log(this.module, `压缩资源路径:${srcPath}`);
        // 初始化队列
        this.compressTasks = [];
        //遍历项目资源
        if (existsSync(srcPath)) {
            this.compress(srcPath, this.isCompress.bind(this));
        }

        await Promise.all(this.compressTasks);
        this.compressTasks.length = 0;
        this.compressTasks = [];
        //打印结果
        this.logger.log(this.module, `压缩完成，正在生成日志...`);
        this.printResults();
        this.onCompressComplete();
    }

    public async validateAndCompress() {
        await this.validate();
        await this.startCompress();
    }
}